import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { File } from './entities/file.entity';
import { Repository } from 'typeorm';
import { FilePathDto } from './dto/file-path.dto';
import { join, resolve } from 'path';
import { writeFile } from 'fs/promises';
import { BaseService } from '../base/base.service';
import { CreateFileDto } from './dto/create-file.dto';
import { PageQuery } from '../../../util/entity/page.entity';
import { buildQuery } from '../../../util/common-fn';
import md5 = require('md5');

@Injectable()
export class FileService {
  // 默认写入文件的路径
  private readonly defaultWritePath = resolve('public/images');
  // 默认请求路径
  // private readonly defaultUrlPath = 'http://localhost:3000/api/public/images/';
  private readonly defaultUrlPath =
    'http://192.168.1.2:3000/api/public/images/';

  constructor(
    @InjectRepository(File)
    private fileRepository: Repository<File>,
    private baseService: BaseService,
  ) {}

  /**
   * 记录上传的这张照片
   */
  private async recordFile(createFileDto: CreateFileDto) {
    return await this.fileRepository.save(createFileDto);
  }

  /**
   * 标记这张照片已经使用
   * @param shorUrl
   */
  async setUsed(shorUrl: string) {
    if (shorUrl) {
      await this.fileRepository.update({ shorUrl }, { used: 1 });
      return true;
    }
    return false;
  }

  /**
   * 把文件写入某个地方,默认写入在public/images下面
   * @param file 文件
   * @param folder 写入的路径,传入images下面的一个文件夹名称即可,比如weapons 或者weapons/sword
   */
  async writeFileTo(file: Express.Multer.File, folder = '') {
    // 拼接文件路径比如 /Users/xr/myProject/folder
    const baseFilePath = join(this.defaultWritePath, folder);
    // 文件后缀名称,匹配.jpg这样的
    const suffixName = this.baseService.matchOne(
      file.originalname,
      /\.[A-z\d]+$/,
    );
    const fileName = this.baseService.getUuid() + suffixName;
    // 文件最终路径,比如 /Users/xr/Downloads/***/cat.jpg
    const fullPath = join(baseFilePath, fileName);
    try {
      // md5加密后的字符
      const hash = md5(file.buffer);
      // 通过hash找到的数据库文件
      const resultFile = await this.findByHash(hash);
      // 如果文件存在,直接返回数据库里的数据
      if (resultFile) {
        return new FilePathDto(resultFile.shorUrl, resultFile.fullUrl);
      } else {
        // 如果文件不存在,写入文件,并记录在数据库
        await writeFile(fullPath, file.buffer);
        // 准备需要创建的数据
        const createFileDto = new CreateFileDto(
          join(folder, fileName),
          this.defaultUrlPath + join(folder, fileName),
          hash,
          folder,
          file.size,
        );
        const record = await this.recordFile(createFileDto);
        return new FilePathDto(record.shorUrl, record.fullUrl);
      }
    } catch (err) {
      throw new Error('写入文件出错:' + err.message);
    }
  }

  /**
   * 通过md5查找文件
   * @private
   */
  private async findByHash(hash: string) {
    return await this.fileRepository.findOneBy({ hash });
  }

  /**
   * 从数据库中找到哪个文件夹里的所有图片
   * @param folder
   */
  getViews(folder: string) {
    return this.fileRepository.find({
      where: { folder },
      order: {
        createDate: 'desc',
      },
      take: 50,
    });
  }

  /**
   * 查找所有
   * @param pageQuery
   */
  findAll(pageQuery: PageQuery<File>) {
    return this.fileRepository.findAndCount(buildQuery(pageQuery));
  }

  getFolders() {
    return this.fileRepository
      .createQueryBuilder('file')
      .select('distinct file.folder')
      .orderBy({ 'file.folder': 'ASC' })
      .getRawMany();
  }
}
